#include <stdio.h>
#include <stdlib.h> 
#include <sys/time.h>
#include <math.h>
#include <GLUT/glut.h> 
#include <Accelerate/Accelerate.h>
#include <dispatch/dispatch.h>

#define kWindowWidth      800 
#define kWindowHeight     800
#define animateSteps      100
// making these power of 2 let
// us perform % with mask instead
#define OUTERLOOP         (0x100000)
#define INNERLOOP         (8)
#define GCD_SIZE          (8)

#define PI 3.14159265

float FrameBuffer[GCD_SIZE][kWindowHeight][kWindowWidth];

float a,b,c,d;
float a0, b0, c0, d0;
float deltaA, deltaB, deltaC, deltaD;

const vFloat vzero = (vFloat){0.f, 0.f, 0.f, 0.f};
const vFloat vxfactor = (vFloat){kWindowWidth, kWindowWidth, kWindowWidth, kWindowWidth};
const vFloat vyfactor = (vFloat){kWindowHeight, kWindowHeight, kWindowHeight, kWindowHeight};
const vFloat vhalf = (vFloat){0.5f, 0.5f, 0.5f, 0.5f};
const vFloat vquarter = (vFloat){0.25f, 0.25f, 0.25f, 0.25f};
const vFloat vtwo = (vFloat){2.f, 2.f, 2.f, 2.f};
const vFloat vrandmax = (vFloat){(float)RAND_MAX, (float)RAND_MAX, (float)RAND_MAX, (float)RAND_MAX};
const vFloat vmax = (vFloat){2.f, 2.f, 2.f, 2.f};
const vFloat vmin = (vFloat){-2.f, -2.f, -2.f, -2.f};

inline float random(float min, float max)
{
  return (float)rand() / (float)RAND_MAX * (float)(max-min) + (float)min;
}

void setRandom()
{
  a = random(0, PI);
  b = random(0, PI);
  c = random(0, PI);
  d = random(0, PI);
}

void keyboard(unsigned char key, int x, int y)
{
  if (key == 27)
    glutPositionWindow(100, 100);  
}

void display()
{
  static timeval t0 = {0};
  timeval t1;
  gettimeofday(&t1, NULL);
  if (t0.tv_sec != 0)
  {
    printf("%f\n", (double)(t1.tv_sec-t0.tv_sec)+(double)(t1.tv_usec-t0.tv_usec)/1000000);
  }
  t0 = t1;

  static unsigned int times = 0;

  if (times == animateSteps)
  {
    times = 0;
  }

  if (times == 0)
  {
    a0 = a;
    b0 = b;
    c0 = c;
    d0 = d;

    setRandom();

    deltaA = (a - a0)/animateSteps;
    deltaB = (b - b0)/animateSteps;
    deltaC = (c - c0)/animateSteps;
    deltaD = (d - d0)/animateSteps;

    a = a0;
    b = b0;
    c = c0;
    d = d0;
  }

  glClear(GL_COLOR_BUFFER_BIT);
  
  memset(FrameBuffer, 0, sizeof(FrameBuffer));

  vFloat va = (vFloat){a,a,a,a};
  vFloat vb = (vFloat){b,b,b,b};
  vFloat vc = (vFloat){c,c,c,c};
  vFloat vd = (vFloat){d,d,d,d};

  dispatch_apply(GCD_SIZE, dispatch_get_global_queue(0,0), ^(size_t iter) 
  {
    for (unsigned int i=iter*OUTERLOOP/GCD_SIZE; i<(iter+1)*OUTERLOOP/GCD_SIZE; i+=4)
    {
      vFloat vx = (vFloat){rand(), rand(), rand(), rand()};
      vFloat vy = (vFloat){rand(), rand(), rand(), rand()};
      vFloat x = vx / vrandmax * (vmax - vmin) + vmin;
      vFloat y = vy / vrandmax * (vmax - vmin) + vmin;
      
      for (int j=0; j<INNERLOOP; j++)
      {
        vFloat xn = vsinf(y * va) - vcosf(x * vb);
        vFloat yn = vsinf(x * vc) - vcosf(y * vd);

        x = xn;
        y = yn;
        
        vFloat coordx = (x + vtwo) * vquarter * vxfactor; 
        vFloat coordy = (y + vtwo) * vquarter * vyfactor;
        
        for (int vi=0; vi<4; vi++)
        {
          //FrameBuffer[iter][(int)((float *)&coordy)[vi]][(int)((float *)&coordx)[vi]] += 0.001f;
          FrameBuffer[iter][(int)((float *)&coordy)[vi]][(int)((float *)&coordx)[vi]] += 0.004f;
        }
      }
    }
  });

  dispatch_apply(GCD_SIZE, dispatch_get_global_queue(0,0), ^(size_t iter) 
  {
    for (unsigned int y=iter*kWindowHeight/GCD_SIZE; y<(iter+1)*kWindowHeight/GCD_SIZE; y++)
    {
      for (int x=0; x<kWindowWidth; x+=4)
      {
        vFloat sum = vzero;
        for (int j=0; j<GCD_SIZE; j++)
        {
          sum += *(vFloat *)&(FrameBuffer[j][y][x]);
        }
        FrameBuffer[0][y][x+0] = ((float *)(&sum))[0];
        FrameBuffer[0][y][x+1] = ((float *)(&sum))[1];
        FrameBuffer[0][y][x+2] = ((float *)(&sum))[2];
        FrameBuffer[0][y][x+3] = ((float *)(&sum))[3];
      }
    }
  });
  
  // GL draw pixels here...
  glDrawPixels(kWindowWidth, kWindowHeight, GL_LUMINANCE, GL_FLOAT, FrameBuffer);
  
  glutSwapBuffers();

  a += deltaA;
  b += deltaB;
  c += deltaC;
  d += deltaD;

  times++;
} 

void graphicsSetup(void)
{
  float subColor = 1.0f/255;
  
  // clear and set color
  glClearColor(1, 1, 1, 1);
  glClear(GL_COLOR_BUFFER_BIT);
  glColor4f(subColor, subColor, subColor, 0);

  // setup alpha
  glEnable(GL_BLEND);
  glBlendEquation(GL_FUNC_REVERSE_SUBTRACT);
  glBlendFunc(GL_ONE, GL_ONE);

  // load identity
  glLoadIdentity();
}

int main(int argc, char** argv) 
{
  //setup initial values
  srand( time(NULL) );
  
  glutInit(&argc, argv); 
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); 
  glutInitWindowSize(kWindowWidth, kWindowHeight); 
  glutInitWindowPosition(100, 100); 
  glutCreateWindow("Peter De Jong");

  //glutFullScreen();
  
  graphicsSetup();

  setRandom();

  glutKeyboardFunc(keyboard);

  glutDisplayFunc(display);

  glutIdleFunc(display);

  glutMainLoop();

  return 0;
}
